﻿#include "H264Encoder.h"

#pragma comment(lib,"libx264.lib")

/** 初始化编码器，并返回一个编码器对象，注意参数设置顺序 */
void h264_compress_begin(H264Encoder* en, int width, int height)
{
    if ((en->param->i_width + en->param->i_height) != 0 &&
        en->param->i_width != width &&
        en->param->i_height != height)
    {
        h264_compress_end(en);
    }

    // 设置默认参数
    x264_param_default(en->param);

    // 设置即时编码
    x264_param_default_preset(en->param, x264_preset_names[2], x264_tune_names[7]);

    // 宽高比的两个值相对互素，且在[0, 65535] 之间
    en->param->i_width = width;
    en->param->i_height = height;

    // 编码比特流的CSP
    en->param->i_csp = X264_CSP_I420;

    /**
    * 值为true，则NALU之前是4字节前缀码0x00000001
    * 针对本程序，b_annexb必须为1，编出来的NALU前4个字节为分隔用的前缀码
    * 后面mp4v2在判断NALU类型的时候要用到，设置为0则表示NALU的长度，会导致无法判断是什么类型的帧
    */
    en->param->b_annexb = 1;

    /**
    * 每个关键帧前都发送sps和pps
    * 使用实时视频传输时，需要实时发送sps,pps数据
    * 重复SPS/PPS 放到关键帧前面
    * 该参数设置是让每个I帧都附带sps/pps。
    */
    en->param->b_repeat_headers = 1;

    // 熵编码, 自适应上下文算术编码，baseline不支持  
    en->param->b_cabac = 1;

    // 处理线程个数
    en->param->i_threads = 1;

    // 在此间隔设置IDR关键帧(2秒刷新一个I帧)
    en->param->i_keyint_max = en->frame_rate * 2;

    // 帧率的分母
    en->param->i_fps_den = 1;

    // 帧率的分子
    en->param->i_fps_num = en->frame_rate;

    // b_mb_tree不为0, 将导致编码延时帧,在实时编码时,必须为0.
    en->param->rc.b_mb_tree = 0;

    /**
    * 码率控制
    * 参数i_rc_method表示码率控制，CQP(恒定质量)，CRF(恒定码率)，ABR(平均码率)
    */
    en->param->rc.i_rc_method = X264_RC_ABR;

    /**
    * 图像质量控制
    * rc.f_rf_constant 是实际质量，越大图像越花，越小越清晰。
    * param.rc.f_rf_constant_max 图像质量的最大值。
    */
    en->param->rc.f_rf_constant = 25;
    en->param->rc.f_rf_constant_max = 45;

    // 编码复杂度
    en->param->i_level_idc = 30;

    // 平均码率模式下，最大瞬时码率，默认0(与-B设置相同)
    en->param->rc.i_vbv_max_bitrate = en->bit_rate * 1.2;
    en->param->rc.i_bitrate = en->bit_rate;

    // log级别
    en->param->i_log_level = X264_LOG_NONE;

    // 设置Profile，这里有5种级别（编码出来的码流规格），级别越高，清晰度越高，耗费资源越大
    x264_param_apply_profile(en->param, x264_profile_names[1]);

    /** 
    * 打开编码器句柄
    * 可通过 x264_encoder_parameters 得到设置给X264的参数.
    * 可通过 x264_encoder_reconfig 更新X264的参数
    */
    if ((en->handle = x264_encoder_open(en->param)) == 0) 
    {
        return;
    }

    // 每帧图片数量(yuv)
    en->picture_in->img.i_plane = 3;

    // 为图像结构体x264_picture_t分配内存
    x264_picture_alloc(en->picture_in, en->param->i_csp, en->param->i_width, en->param->i_height);
}

/** 编码一帧 */
int h264_encode_frame(H264Encoder* en, uint8_t* in)
{
    int y_size = en->param->i_width * en->param->i_height;

    // 写入Y
    memcpy(en->picture_in->img.plane[0], in, y_size);

    // 写入U
    memcpy(en->picture_in->img.plane[1], in + y_size, y_size / 4);

    // 写入V
    memcpy(en->picture_in->img.plane[2], in + y_size + y_size / 4, y_size / 4);

    en->picture_in->i_pts++;

    // 编码一帧图像，nal为返回的码流数据，nal_count返回的nal单元的数目
    en->nal = nullptr;
    en->nal_count = -1;
    int i_frame_size = x264_encoder_encode(en->handle, &en->nal, &en->nal_count, en->picture_in, en->picture_out);

    return i_frame_size;
}

/** 输出最后一帧数据编码中编码器中剩余的码流数据 **/
int h264_flush_encoder(H264Encoder* en)
{
    int nNal = -1;

    x264_picture_t pic_out = { 0 };

    // 获取X264中缓冲帧数.
    int iFrames = x264_encoder_delayed_frames(en->handle);
    printf("curent delayed frames:%d \n", iFrames);

    // 刷新延迟帧
    while (x264_encoder_delayed_frames(en->handle))
    {
        int i_frame_size = x264_encoder_encode(en->handle, &(en->nal), &nNal, NULL, &pic_out);

        if (i_frame_size < 0) 
        {
            perror("error x264_encoder_encode \n");
            return -1;
        }
        else if (i_frame_size)
        {
            return i_frame_size;
        }
    }

    return 0;
}

/** 释放内存 */
void h264_compress_end(H264Encoder* en)
{
    if (en->param)
    {
        memset(en->param, 0, sizeof(x264_param_t));
    }

    if (en->handle) {
        h264_flush_encoder(en);
        x264_encoder_close(en->handle);
        en->handle = nullptr;
        en->nal = nullptr;
    }

    if (en->picture_in) {
        x264_picture_clean(en->picture_in);
        memset(en->picture_in, 0, sizeof(x264_picture_t));
    }
}